package itc4j;

import java.io.Serializable;

/**
 * @author Sina Bagherzadeh
 */
public final class Stamp implements Serializable {
    private ID id;
    private Event event;

    public Stamp() {
        id = new ID();
        event = new Event(0);
    }

    private Stamp(ID id, Event event) {
        this.id = id;
        this.event = event;
    }

    public static boolean leq(Stamp s1, Stamp s2) {
        return Event.leq(s1.event, s2.event);
    }

    /**
     * 
     * @param s
     * @return an array with size two containing new stamp generated by forking stamp s
     */
    public static Stamp[] fork(Stamp s) {
        Stamp[] result = new Stamp[2];
        ID[] ids = ID.split(s.id);
        result[0] = new Stamp(ids[0], s.event.clone());
        result[1] = new Stamp(ids[1], s.event.clone());
        return result;
    }

    public static Stamp[] peek(Stamp s) {
        return new Stamp[]{new Stamp(null, s.event.clone()), new Stamp(s.id.clone(), s.event.clone())};
    }

    public static Stamp join(Stamp s1, Stamp s2) {
        return new Stamp(ID.sum(s1.id, s2.id), Event.join(s1.event, s2.event));
    }

    private static Event fill(ID id, Event event) {
        if (id == null)
            return event.clone();
        else {
            if (id.left == null && id.right == null)
                return new Event(Event.max(event));
            if (event.right == null && event.left == null)
                return new Event(event.value);
            if (id.left != null && id.left.left == null && id.left.right == null) {
                Event er = fill(id.right, event.right);
                int max = Math.max(Event.max(event.left), Event.min(er));
                return Event.norm(new Event(new Event(max), er, event.value));
            }
            if (id.right != null && id.right.left == null && id.right.right == null) {
                Event el = fill(id.left, event.left);
                int max = Math.max(Event.max(event.right), Event.min(el));
                return Event.norm(new Event(el, new Event(max), event.value));
            }
            return Event.norm(new Event(fill(id.left, event.left),
                    fill(id.right, event.right), event.value));
        }
    }

    private static GrowResult grow(ID id, Event event) {
        if (event.left == null && event.right == null) {
            if (id != null && id.left == null && id.right == null)
                return new GrowResult(new Event(event.value + 1), 0);
            if (id != null) {
                GrowResult er = grow(id.clone(), new Event(new Event(0), new Event(0), event.value));
                er.c += event.maxDepth() + 1;
                return er;
            }
        } else {
            if (id.left == null && id.right != null) {
                GrowResult er = grow(id.right.clone(), event.right.clone());
                Event e = new Event(event.left.clone(), er.event, event.value);
                return new GrowResult(e, er.c + 1);
            } else if (id.left != null && id.right == null) {
                GrowResult er = grow(id.left.clone(), event.left.clone());
                Event e = new Event(er.event, event.right.clone(), event.value);
                return new GrowResult(e, er.c + 1);
            } else {
                GrowResult left = grow(id.left.clone(), event.left.clone());
                GrowResult right = grow(id.right.clone(), event.right.clone());
                if (left.c < right.c) {
                    Event e = new Event(left.event, event.right.clone(), event.value);
                    return new GrowResult(e, left.c + 1);
                } else {
                    Event e = new Event(event.left.clone(), right.event, event.value);
                    return new GrowResult(e, right.c + 1);
                }
            }
        }
        return null;
    }

    public static Stamp event(Stamp s) {
        Event e = fill(s.id, s.event);
        if (!s.event.equals(e))
            return new Stamp(s.id.clone(), e);
        else {
            GrowResult gr = grow(s.id, s.event);
            return new Stamp(s.id.clone(), gr.event);
        }
    }

    public Stamp[] send(Stamp s) {
        return peek(event(s));
    }

    public Stamp receive(Stamp s1, Stamp s2) {
        return event(join(s1, s2));
    }

    public Stamp[] sync(Stamp s1, Stamp s2) {
        return fork(join(s1, s2));
    }

    public String toString() {
        return "(" + id + ", " + event + ")";
    }
}
